#include <SDL/SDL.h>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <algorithm>
#include "MRMesh.h"
#include "d_Math.h"
using namespace std;
using namespace d_Math;

typedef void (APIENTRY * GL_PointParameterfvARB_Func)(GLenum, const GLfloat *);
typedef void (APIENTRY * GL_PointParameterfARB_Func)(GLenum, const GLfloat);
GL_PointParameterfvARB_Func glPointParameterfvARB_ptr = 0;
GL_PointParameterfARB_Func glPointParameterfARB_ptr = 0;

MRMesh::MRMesh(char *filename)
{
	chdir(filename);
	FILE *pads_file = fopen("pads", "r");
	fscanf(pads_file, "PADS Format\n");
	fscanf(pads_file, "patches %i\n", &num_charts);
	look_up = (int *) malloc (num_charts*sizeof(int));
	bb_w = (int *) malloc(num_charts*sizeof(int));
	bb_h = (int *) malloc(num_charts*sizeof(int));
	bb_x = (int *) malloc(num_charts*sizeof(int));
	bb_y = (int *) malloc(num_charts*sizeof(int));
	dist = (float *) malloc(num_charts*sizeof(float));
	dx = (float *) malloc(num_charts*sizeof(float));

	fscanf(pads_file, "cameras %i\n", &num_cams);
	cameras = (MRCamera *) malloc (num_cams*sizeof(MRCamera));
	for(int i=0; i<num_cams; i++)
	{
		float tmp[3];
		fscanf(pads_file, "%f %f %f\n", &tmp[0], &tmp[1], &tmp[2]);
		for(int j=0; j< 3; j++)
			cameras[i].vp[j]=tmp[j];
		fscanf(pads_file, "%f %f %f\n", &tmp[0], &tmp[1], &tmp[2]);
		for(int j=0; j< 3; j++)
			cameras[i].xa[j]=tmp[j];
		fscanf(pads_file, "%f %f %f\n", &tmp[0], &tmp[1], &tmp[2]);
		for(int j=0; j< 3; j++)
			cameras[i].ya[j]=tmp[j];
		fscanf(pads_file, "%f %f %f\n", &tmp[0], &tmp[1], &tmp[2]);
		for(int j=0; j< 3; j++)
			cameras[i].d[j]=tmp[j];
		fscanf(pads_file, "%f %f %f %f %f\n", &cameras[i].sx, &cameras[i].sy, &cameras[i].cx, &cameras[i].cy, &cameras[i].focal);
	}
	for(int i=0; i<num_charts; i++)
	{
		int tmp[5];
		fscanf(pads_file, "%i %i\n", &tmp[0], &tmp[1]);
		look_up[tmp[0]] = tmp[1];
		fscanf(pads_file, "%i %i %i %i\n", &tmp[1], &tmp[2], &tmp[3], &tmp[4]);
		bb_w[tmp[0]] = tmp[1];
		bb_h[tmp[0]] = tmp[2];
		bb_x[tmp[0]] = tmp[3];
		bb_y[tmp[0]] = tmp[4];
		float tmp2[2];
		fscanf(pads_file, "%f %f\n", &tmp2[0], &tmp2[1]);
		dist[tmp[0]] = tmp2[0];
		dx[tmp[0]] = tmp2[1];
	}
	fclose(pads_file);

	p = (Image **) malloc (num_charts * sizeof(Image *));
	for(int i=0; i<num_charts; i++)
	{
		char patch_name[40];
		sprintf(patch_name, "%i_P.gif", i);
		p[i] = new Image(bb_w[i], bb_h[i]);
		p[i]->Load(patch_name);
	}

	n = (Image **) malloc (num_charts * sizeof(Image *));
	for(int i=0; i<num_charts; i++)
	{
		char patch_name[40];
		sprintf(patch_name, "%i_N.gif", i);
		n[i] = new Image(bb_w[i], bb_h[i]);
		n[i]->Load(patch_name);
	}

	c = (Image **) malloc (num_charts * sizeof(Image *));
	for(int i=0; i<num_charts; i++)
	{
		char patch_name[40];
		sprintf(patch_name, "%i_C.gif", i);
		c[i] = new Image(bb_w[i], bb_h[i]);
		c[i]->Load(patch_name);
	}

	glPointParameterfvARB_ptr=(GL_PointParameterfvARB_Func) SDL_GL_GetProcAddress("glPointParameterfvARB");
	glPointParameterfARB_ptr=(GL_PointParameterfARB_Func) SDL_GL_GetProcAddress("glPointParameterfARB");
	glPointParameterfARB_ptr(GL_POINT_SIZE_MIN_ARB, 1);
	glPointParameterfARB_ptr(GL_POINT_SIZE_MAX_ARB, 20000);
	//GLfloat p_size[] = {0,0,1.0/100000.0};
	GLfloat p_size[] = {0,0,1.0/4.0};
	glPointParameterfvARB_ptr(GL_POINT_DISTANCE_ATTENUATION_ARB, p_size);

	last_draw = -1;
	current_depth = 0;

	charts = (MRChart **) malloc (num_charts * sizeof(MRChart *));

	int min_chart = get_minchart();

	get_pointdist(min_chart,0);

	tree_depth = get_treedepth(min_chart, 1);

	current_depth=0;

	point_sizes.set_size(tree_depth+1);

	for (int k=0; k<num_charts; k++) 
	{
		d_Vecf local_sizes;
		local_sizes.set_size(tree_depth+1);
		int tt_size = get_targetsize(min_chart);
		int sections_w = get_top_of_chartw(k, tt_size);
		int sections_h = get_top_of_charth(k, tt_size);
		charts[k] = new MRChart(k, tree_depth, min_chart, sections_w, sections_h, 
								dx[k], dist[k], bb_x[k], bb_y[k], cameras[look_up[k]],
								p[k], n[k], c[k], &splats);

		local_sizes = charts[k]->get_local_sizes(tree_depth+1);
		
		point_sizes = point_sizes + local_sizes;
	}

	point_sizes = point_sizes/(float)num_charts;	

	glPointSize(point_sizes[current_depth]*1000);

	point_sizes.print();

	compute_aabb();
}

void MRMesh::draw(float target_fps, Camera cam)
{
	float start_time = SDL_GetTicks();

	if(cam.mMoving||frustum.first_time)
	{
		frustum.setCamInternals(cam.mPerspective[0], cam.mPerspective[1],cam.mPerspective[2], cam.mPerspective[3]);
		frustum.setCamDef(cam.GetPosition(), cam.GetCenter(), cam.GetUp());
		frustum.first_time=false;
		frustum.changed=true;
	}
	else
		frustum.changed=false;
	if(frustum.boxInFrustum(aabb)==INSIDE)
	{
		//float start_time = SDL_GetTicks();
		glBegin(GL_POINTS);
		for(int i=0; i<num_charts; i++)
			charts[i]->draw(current_depth, &frustum);
		glEnd();
		//float time_elapsed = target_fps*(float)(SDL_GetTicks()-start_time)/1000.0;
		//if(time_elapsed<0.4)
		//	depth_change(1);
		//if(time_elapsed>3.5)
		//	depth_change(-1);
	}

	float time_elapsed = target_fps*(float)(SDL_GetTicks()-start_time)/1000.0;
	if(time_elapsed<0.6)
		depth_change(1);
	if(time_elapsed>3)
		depth_change(-1);
}

void MRMesh::draw(Camera cam)
{
	if(cam.mMoving||frustum.first_time)
	{
		frustum.setCamInternals(cam.mPerspective[0], cam.mPerspective[1],cam.mPerspective[2], cam.mPerspective[3]);
		frustum.setCamDef(cam.GetPosition(), cam.GetCenter(), cam.GetUp());
		frustum.first_time=false;
		frustum.changed=true;
	}
	else
		frustum.changed=false;
	if(frustum.boxInFrustum(aabb)==INSIDE)
	{
		glBegin(GL_POINTS);
		for(int i=0; i<num_charts; i++)
			charts[i]->draw(current_depth, &frustum);
		glEnd();
	}
}

void MRMesh::draw()
{
	glBegin(GL_POINTS);
	for(int i=0; i<num_charts; i++)
		charts[i]->draw(current_depth);
	glEnd();
}


//////////////////AUX FUNCS/////////////////////////////
void MRMesh::drawColor()
{
	glDisable(GL_LIGHTING);
	glBegin(GL_POINTS);
	for(int i=0; i<num_charts; i++)
		charts[i]->drawColor(current_depth);
	glEnd();
}

void MRMesh::drawNormals()
{
	glDisable(GL_LIGHTING);
	glBegin(GL_POINTS);
	for(int i=0; i<num_charts; i++)
		charts[i]->drawNormals(current_depth);
	glEnd();
}

void MRMesh::drawSmoothNormals()
{
	glDisable(GL_LIGHTING);
	glBegin(GL_POINTS);
	for(int i=0; i<num_charts; i++)
		charts[i]->drawSmoothNormals(current_depth);
	glEnd();
}

void MRMesh::drawID()
{
	glDisable(GL_LIGHTING);
	glBegin(GL_POINTS);
	for(int i=0; i<num_charts; i++)
		charts[i]->drawID(current_depth);
	glEnd();
}

void MRMesh::setSplatColor(int id, float r, float g, float b)
{
	splats[id]->c[0]=r;
	splats[id]->c[1]=g;
	splats[id]->c[2]=b;
}

void MRMesh::setSplatNormal(int id, float xd, float yd, float zd)
{
	splats[id]->n[0]=xd;
	splats[id]->n[1]=yd;
	splats[id]->n[2]=zd;
}

/////////////////////////////////////////////////////////

void MRMesh::compute_aabb()
{
	d_Vec3f min = d_Vec3f(10000,10000,10000);
	d_Vec3f max = d_Vec3f(-10000,-10000,-10000);
	
	for(int i=0; i<num_charts; i++)
	{
		Splat avg = charts[i]->get_average_splat();
		if(avg.vp[0]<min[0])
			min[0] = avg.vp[0];
		if(avg.vp[0]>max[0])
			max[0] = avg.vp[0];
		if(avg.vp[1]<min[1])
			min[1] = avg.vp[1];
		if(avg.vp[1]>max[1])
			max[1] = avg.vp[1];
		if(avg.vp[2]<min[2])
			min[2] = avg.vp[2];
		if(avg.vp[2]>max[2])
			max[2] = avg.vp[2];
	}
	aabb = new d_AABB(min,max);
}

d_Vec3f MRMesh::get_center()
{
	float xmin=10000, xmax=-10000;
	float ymin=10000, ymax=-10000;
	float zmin=10000, zmax=-10000;
	for(int i=0; i<num_charts; i++)
	{
		d_Vec3f aux = charts[i]->get_average_splat().vp;
		if(aux[0]<xmin)
			xmin = aux[0];
		if(aux[0]>xmax)
			xmax = aux[0];
		if(aux[1]<ymin)
			ymin = aux[1];
		if(aux[1]>ymax)
			ymax = aux[1];
		if(aux[2]<zmin)
			zmin = aux[2];
		if(aux[2]>zmax)
			zmax = aux[2];
	}
	return d_Vec3f(xmin+(float)(xmax-xmin)/2.0, ymin + (float)(ymax-ymin)/2.0, zmin+(float)(zmax-zmin)/2.0);
}

float MRMesh::get_realbbw(int cindex)
{
	return bb_w[cindex]*cameras[look_up[cindex]].sx;
}

float MRMesh::get_realbbh(int cindex)
{
	return bb_h[cindex]*cameras[look_up[cindex]].sy;
}

//////////////////////////////
float MRMesh::get_pointdist(int minchart, int level)
{
	cout << "Point distance: " << cameras[look_up[minchart]].sx << endl;
	return get_realbbw(minchart)/bb_w[minchart];
}
//////////////////////////////

int MRMesh::get_minchart()
{
	int min;
	float tmp = 100000;
	for(int i=0; i<num_charts; i++)
		if(get_realbbw(i)*get_realbbh(i)<tmp)
		{
			tmp = get_realbbw(i)*get_realbbh(i);
			min = i;
		}
	return min;
}

int MRMesh::get_treedepth(int index, float target_size)
{
	int depth=0;
	int w = bb_w[index], h=bb_h[index];
	while(w>target_size && h>target_size)
	{
		w = (int)((float)w/2.0);
		h = (int)((float)h/2.0);
		depth++;
	}
	return depth;
}

int MRMesh::get_targetsize(int index)
{
	if(bb_w[index]>bb_h[index])
		return bb_h[index];
	else
		return bb_w[index];
}

int MRMesh::get_top_of_chartw(int index, int size)
{
	int ret=0;
	float w=bb_w[index];
	while(w>=size)
	{
		w -= size;
		ret++;
	}
	return ret;
}

int MRMesh::get_top_of_charth(int index, int size)
{
	int ret=0;
	float h=bb_h[index];
	while(h>=size)
	{
		h -= size;
		ret++;
	}
	return ret;
}

void MRMesh::depth_change(int dir)
{
	if(dir==-1)
	{
		if(current_depth==0)
			return;
		current_depth--;
	}
	if(dir==1)
	{
		if(current_depth==tree_depth)
		return;
		current_depth++;
	}
	float t;
	switch(current_depth)
	{
	case 0:
		t=10000000;
		break;
	case 1:
		t=4000000;
		break;
	case 2:
		t=1000000;
		break;
	case 3:
		t=300000;
		break;
	case 4:
		t=200000;
		break;
	}
	frustum.first_time = true;
	glPointSize(point_sizes[current_depth]*1000);
	//GLfloat p_size[] = {0,0,1.0/t};
	//glPointParameterfvARB_ptr(GL_POINT_DISTANCE_ATTENUATION_ARB, p_size);
}
